﻿using System;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data.Linq.SqlClient;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;

namespace PickGold.Data.Linq
{
	public class MappingModel : MetaModel
	{
		private Type _Context;
		private string _Prefix;
		private string _Suffix;
		private string _Schema;
		private MetaSource _Source;
		private MappingTable[] _Tables;
		private MappingFunction[] _Functions;

		public MappingModel(MetaSource source, Type context)
		{
			this._Source = source;
			this._Context = context;
		}

		public override Type ContextType
		{
			get
			{
				return this._Context;
			}
		}

		public override string DatabaseName
		{
			get
			{
				if (this._Source.Config == null)
					return this._Context.Name;

				var a = this._Source.Config.Attributes[MetaSource.Name];
				if (a == null)
					return this._Context.Name;

				if (string.IsNullOrWhiteSpace(a.Value))
					return this._Context.Name;

				return a.Value;
			}
		}

		public override MetaFunction GetFunction(MethodInfo method)
		{
			return this.GetMethod(method);
		}

		public MappingFunction GetMethod(MethodInfo method)
		{
			return new MappingFunction(this._Source, this, method);
		}

		public override IEnumerable<MetaFunction> GetFunctions()
		{
			if (this._Functions != null)
			{
				foreach (var f in this._Functions)
					yield return f;
				yield break;
			}

			var ls = new List<MappingFunction>();
			if (this._Source.Config == null)
			{
				var ms = this._Context.GetMethods();
				foreach (var m in ms)
				{
					var c = m.GetCustomAttributes(typeof(FunctionAttribute), true);
					if (c.Length > 0)
						ls.Add(this.GetMethod(m));
				}
				foreach (var f in this._Functions)
					yield return f;
				yield break;
			}

			var ns = this._Source.Config.SelectNodes(MetaSource.Function);
			foreach (XmlNode n in ns)
			{
				var a = n.Attributes[MetaSource.Method];
				if (a == null || string.IsNullOrWhiteSpace(a.Value))
				{
					a = n.Attributes[MetaSource.Name];
					if (a == null || string.IsNullOrWhiteSpace(a.Value))
						continue;
				}
				var m = this._Context.GetMethod(a.Value);
				if (m == null)
					continue;

				//var c = m.GetCustomAttributes(typeof(FunctionAttribute), true);
				//if (c.Length > 0)
				//    yield return this.GetFunction(m);
				ls.Add(this.GetMethod(m));
			}
			foreach (var f in this._Functions)
				yield return f;
		}

		public override MetaType GetMetaType(Type type)
		{
			return this.GetModel(type);
		}

		public MappingType GetModel(Type type)
		{
			return new MappingType(this._Source, this, type);
		}

		public override MetaTable GetTable(Type rowType)
		{
			return this.GetEntity(rowType);
		}

		public MappingTable GetEntity(Type rowType)
		{
			return new MappingTable(this._Source, this, rowType);
		}

		public override IEnumerable<MetaTable> GetTables()
		{
			if (this._Tables != null)
			{
				foreach (var item in this._Tables)
					yield return item;
				yield break;
			}

			var ls = new List<MappingTable>();
			if (this._Source.Config == null)
			{
				var ps = this._Context.GetProperties();
				foreach (var p in ps)
				{
					if (p.PropertyType.GetGenericTypeDefinition() == typeof(Table<>))
						continue;

					var ts = p.PropertyType.GetGenericArguments();
					if (ts == null || ts.Length != 1 || ts[0] == null)
						continue;

					var c = ts[0].GetCustomAttributes(typeof(TableAttribute), true);
					if (c.Length > 0)
						ls.Add(this.GetEntity(ts[0]));
				}
				foreach (var item in this._Tables)
					yield return item;
				yield break;
			}

			var ns = this._Source.Config.SelectNodes(MetaSource.Table);
			foreach (XmlNode n in ns)
			{
				var a = n.Attributes[MetaSource.Member];
				if (a == null || string.IsNullOrWhiteSpace(a.Value))
					continue;

				var p = this._Context.GetProperty(a.Value);
				if (p == null)
					continue;

				if (p.PropertyType.GetGenericTypeDefinition() == typeof(Table<>))
					continue;

				var ts = p.PropertyType.GetGenericArguments();
				if (ts == null || ts.Length != 1 || ts[0] == null)
					continue;

				var nn = n.SelectSingleNode(MetaSource.Type);
				if (nn == null)
					continue;

				a = nn.Attributes[MetaSource.Name];
				if (a == null || string.IsNullOrWhiteSpace(a.Value))
					continue;

				if (ts[0].Name != a.Value)
					continue;

				//var c = ts[0].GetCustomAttributes(typeof(TableAttribute), true);
				//if (c.Length == 0)
				//    yield return this.GetTable(ts[0]);
				ls.Add(this.GetEntity(ts[0]));
			}
			foreach (var item in this._Tables)
				yield return item;
		}

		public override MappingSource MappingSource
		{
			get
			{
				return this._Source;
			}
		}

		public override Type ProviderType
		{
			get
			{
				if (this._Source.Config == null)
					return typeof(SqlProvider);

				var a = this._Source.Config.Attributes[MetaSource.Provider];
				if (a == null)
					return typeof(SqlProvider);

				if (string.IsNullOrWhiteSpace(a.Value))
					return typeof(SqlProvider);

				return Type.GetType(a.Value);
			}
		}

		public XmlNode GetNode(Type rowType)
		{
			if (this._Source == null || this._Source.Config == null)
				return null;

			var ns = this._Source.Config.SelectNodes(MetaSource.Table);
			foreach (XmlNode n in ns)
			{
				var a = n.Attributes[MetaSource.Member];
				if (a == null || string.IsNullOrWhiteSpace(a.Value))
					continue;

				var p = this._Context.GetProperty(a.Value);
				if (p == null)
					continue;

				if (p.PropertyType.GetGenericTypeDefinition() == typeof(Table<>))
					continue;

				var ts = p.PropertyType.GetGenericArguments();
				if (ts != null && ts.Length == 1 && ts[0] == rowType && n.SelectSingleNode(MetaSource.Type) != null)
					return n;
			}

			return null;
		}

		public XmlNode GetNode(MethodInfo method)
		{
			if (this._Source == null || this._Source.Config == null)
				return null;

			var ns = this._Source.Config.SelectNodes(MetaSource.Function);
			foreach (XmlNode n in ns)
			{
				var a = n.Attributes[MetaSource.Method];
				if (a == null || string.IsNullOrWhiteSpace(a.Value))
					continue;

				if (a.Value == method.Name)
					return n;
			}

			return null;
		}

		public MappingTable GetTable(MappingType type)
		{
			foreach (var item in this._Tables)
			{
				if (item.RowType == type)
					return item;
			}
			return null;
		}
	}
}
